package contegor.scire.core;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;


/**
 * {@link TaskImpl} used to notify a {@link Modifiable} that it must commit the transaction associated with it.
 * 
 * The dependencies this {@link Task} generates are {@link AreLinksAimingTargetId} that will check if still
 * are external {@link Link}s pointing to {@link Identifiable}s in the target {@link Container} marked as
 * erased and that are going to be deleted with this commit.
 * 
 * The consequences this {@link Task} generates are {@link UntrackExternalLink} that will be sent to
 * external {@link Container}s with {@link Identifiable}s no more pointed by {@link Link}s in the current
 * {@link Container} because they have been deleted.  
 * 
 * @version $Revision$
 * @author  Héctor Cirbián Pousa
 */
public class CommitTask extends TaskImpl<Void> implements WithDependencies<Id, Void>, WithConsequences<Void, Void> {
  private static final long serialVersionUID = 5139047627063975345L;

  private List<AreLinksAimingTargetId>  dependencies            = null;
  private StorageResultsReturnPoint<Id> dependenciesReturnPoint = null;
  private List<UntrackExternalLink>     consequences            = null;

  public CommitTask(TaskId taskId, Id targetContainerId) {
    super(targetContainerId);
    this.setTaskId(taskId);
  }

  public CommitTask(Id targetContainerId) {
    super(targetContainerId);
  }

  @Override
  public void start(Container targetContainer, ReturnPoint<? super Void> returnPoint) {
    assert (returnPoint != null) && targetContainer.getId().equals(this.targetContainerId);
    try {
      super.start(targetContainer, returnPoint);
      // Before processing the commit, we check it.
      Map<Id,Set<Id>> shouldBeErased = ((Modifiable)targetContainer).checkCommit(this.taskId.getTransactionId());
      // If there are Identifiables marked as erased, check if there are external Links pointing them. 
      if (shouldBeErased != null) {
        this.dependencies = new LinkedList<AreLinksAimingTargetId>();
        this.dependenciesReturnPoint = new StorageResultsReturnPoint<Id>();
        TaskId subTaskId = this.taskId;
        for (Map.Entry<Id, Set<Id>> identifiableXexternalContainers: shouldBeErased.entrySet()) {
          if (identifiableXexternalContainers.getValue() != null) {
            for (Id externalContainerId: identifiableXexternalContainers.getValue())
              this.dependencies.add(new AreLinksAimingTargetId(subTaskId = subTaskId.next(subTaskId == this.taskId), externalContainerId, identifiableXexternalContainers.getKey()));
          }
        }
      }        
    }
    catch (Exception ex) {
      returnPoint.manage(this.taskId, ex);
    }
  }

  @Override
  public void process(Container targetContainer) throws MissingElementException, TransactionException {
    assert (returnPoint != null) && targetContainer.getId().equals(this.targetContainerId);
    if (dependenciesReturnPoint != null) {
      if ((dependenciesReturnPoint.results != null) && (dependenciesReturnPoint.results.size() > 0)) {
        returnPoint.manage(taskId, new NonDeletableElementException("There are Links pointing elements erased by transaction " + this.taskId.getTransactionId() + " at containers: " + dependenciesReturnPoint.results));
        return;
      }
      if ((dependenciesReturnPoint.exceptions != null) && (dependenciesReturnPoint.exceptions.size() > 0)) {
        for (Exception ex: dependenciesReturnPoint.exceptions)
          returnPoint.manage(taskId, ex);
        return;
      }
    }
    if (targetContainer instanceof Modifiable) {
      List<Id> noMorePointed = ((Modifiable)targetContainer).commit(this.taskId.getTransactionId());
      if ((noMorePointed != null) && (noMorePointed.size() > 0)) {
        TaskId subTaskId = this.taskId;
        this.consequences = new ArrayList<UntrackExternalLink>(noMorePointed.size());
        for (Id id: noMorePointed)
          if (!Id.RAW_ROOT.isAncestorOf(id))
            this.consequences.add(new UntrackExternalLink(subTaskId = subTaskId.next(subTaskId == this.taskId), id.getParentId(), id.getLastIdFragment(), targetContainerId));
      }
    }
  }

  @Override
  public List<? extends Task<Id>> getDependencies() {
    return this.dependencies;
  }

  @Override
  public List<? extends ReturnPoint<? super Id>> getDependenciesReturnPoints() {
    if ((this.dependencies == null) || (this.dependencies.size()==0))
      return Collections.emptyList();
    return new InfiniteSingletonList<ReturnPoint<? super Id>>(this.dependenciesReturnPoint, this.dependencies.size());
  }

  @Override
  public List<? extends Task<Void>> getConsequences() {
    return this.consequences;
  }

  @Override
  public List<? extends ReturnPoint<? super Void>> getConsequencesReturnPoints() {
    if ((this.consequences == null) || (this.consequences.size()==0))
      return Collections.emptyList();
    return new InfiniteSingletonList<ReturnPoint<? super Void>>(this.returnPoint, this.consequences.size());
  }
}